最近业务需要解析excel,使用apache poi库
在vscode中一切顺利。打包为jar丢到生产环境时报错:

1
java.io.IOException: Your InputStream was neither an OLE2 stream, nor an OOXML stream or you haven't provide the poi-ooxml*.jar in the classpath/modulepath - FileMagic: OOXML, having providers: [org.apache.poi.hssf.usermodel.HSSFWorkbookFactory@405294c8]

搜了一圈答案都和实际不符,最后在 https://stackoverflow.com/questions/67884617/apache-poi-excel-writer-works-in-ide-but-not-in-fat-jar-java-io-ioexception-yo 这个帖子下找到了答案

问题的根本是:

Apache POI使用META-INF/services目录下的文件来识别可用的服务提供者。

当使用Maven的maven-assembly-plugin打包成fat JAR时,来自poi和poi-ooxml的重名服务提供者文件没有被正确合并,导致在运行时只有一个提供者可用,无法正确处理Excel文件。

什么是服务提供者呢?这是一种服务发现机制,即 Java SPI (Service Provider Interface) ,允许应用程序动态发现和加载服务实现。它基于以下约定:

  1. 服务提供者在 META-INF/services/ 目录下创建以服务接口全限定名命名的文件

  2. 文件内容是实现该接口的类的全限定名列表

  3. ServiceLoader 类用于加载这些服务实现

例如,如果有一个接口 com.example.MyService,其实现类是 com.example.impl.MyServiceImpl,则会有一个文件:

META-INF/services/com.example.MyService 内容为 com.example.impl.MyServiceImpl

当使用 Maven 打包成 Fat JAR(包含所有依赖的单一 JAR 文件)时,会出现一个问题:

如果多个 JAR 包中都有同名的服务提供者配置文件(如 META-INF/services/com.example.MyService),在合并时这些文件会相互覆盖,导致只有一个文件被保留,其他的实现类信息丢失。

这正是 Apache POI 在打包成 JAR 后出现问题的原因。poi 和 poi-ooxml 模块都有自己的服务提供者配置文件,但在打包时这些文件没有被正确合并。

知道问题之后解决起来就很简单了,我的方案是 将maven-assembly-plugin替换为maven-shade-plugin,并添加ServicesResourceTransformer合并服务提供者文件

修改前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.exceltool.Application</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

修改后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.exceltool.Application</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>

关键的变化是添加了ServicesResourceTransformer,它能够正确合并来自不同JAR包中重名的服务提供者文件,而不是仅保留一个

问题解决